package com.nulldev.util.uri.yt.rewrite;

import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.regex.Pattern;

import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.lang.json.Json;
import com.nulldev.util.lang.json.JsonObject;
import com.nulldev.util.lang.json.JsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.threading.v3.ThreadDispatcher;
import com.nulldev.util.threading.v3_extensions.Task;
import com.nulldev.util.threading.v3_extensions.TaskStatus;
import com.nulldev.util.uri.URIUtil;
import com.nulldev.util.uri.URIUtil2;
import com.nulldev.util.uri.URIUtil2.IO;
import com.nulldev.util.uri.URIUtil2.IO.Entry;
import com.nulldev.util.uri.yt.Format;
import com.nulldev.util.uri.yt.YTSig;
import com.nulldev.util.uri.yt.YtFile;
import com.nulldev.util.uri.yt.YTSig.YTSignatureDecrypter;

import static com.nulldev.util.other.ArrayUtils.contains;
import static com.nulldev.util.java.JVMUtil.async_println;

public class YoutubeExtractor2 {
	
	private static YTSig.YTSignatureDecrypter decipherer;
	
	private static final HashMap<Integer, Format> FORMAT_MAP = new HashMap<Integer, Format>();
	
	public static class EmbedAPIExtractor {
		
		private static final Logger logger = LoggerFactory.getLogger(true, true);
		
		private static String generateURL(final String videoID, final YoutubeExtractionOptions... options) {
			 String ytInfoUrl = (contains(options, YoutubeExtractionOptions.ENFORCE_HTTP_FALLBACK))
					 ? "http://" : "https://";
		     ytInfoUrl += "www.youtube.com/get_video_info?video_id=" + videoID + "&eurl="
		         + URIUtil.safeEncode("https://youtube.googleapis.com/v/" + videoID);
		     if (contains(options, YoutubeExtractionOptions.ENFORCE_ENGLISH_YOUTUBE)) {
		    	 ytInfoUrl += "&hl=en";
		     }
		     return ytInfoUrl;
		}
		
		public static Task<LinkedList<YoutubeVideo>> generateTask(final String videoID, final YoutubeExtractionOptions... options) {
			return new Task<LinkedList<YoutubeVideo>>() {

				@Override
				public LinkedList<YoutubeVideo> run() {
					final LinkedList<YoutubeVideo> results = new LinkedList<YoutubeVideo>();
					async_println("=> Generating URL...");
					final URL url = URIUtil2.parseURL(generateURL(videoID, options));
					Entry[] args = new Entry[0];
					if (contains(options, YoutubeExtractionOptions.SEND_FAKE_COOKIE_DATA)
							|| contains(options, YoutubeExtractionOptions.ENFORCE_PRIVACY_MODE)) {
						args = ArrayUtils.add(args, new Entry("Cookie", StringsUtil.randomString(64)));
					}
					String result;
					try {
						async_println("=> Downloading metadata...");
						result = URIUtil2.PortedLegacyDownloader.downloadPage(url, true, args).getB()
								.replaceAll(Pattern.quote("\\u0026"), "&");
					}catch (Exception e) {
						e.printStackTrace();
						return null;
					}
					async_println("=> Parsing metadata...");
					final Map<String, String> map = URIUtil.queryStringToMap(result);
					if (!map.get("status").equalsIgnoreCase("OK")) {
						return null;
					}
					ArrayUtils.printLln(map);
					final JsonObject main = Json.parse(map.get("player_response")).asObject();
					final JsonObject metadataObject = main.get("videoDetails").asObject();
					String[] tags = new String[metadataObject.get("keywords").asArray().size()];
					int ind = 0;
					for (final JsonValue tag : metadataObject.get("keywords").asArray().values()) {
						tags[ind] = tag.asString();
						ind += 1;
					}
					final YoutubeMetadata metadata = new YoutubeMetadata(
							videoID,
							metadataObject.getString("title"),
							metadataObject.getString("author"),
							metadataObject.getString("channelId"),
							metadataObject.getLong("viewCount", -1),
							metadataObject.getLong("lengthSeconds", -1),
							MathUtil.safeParseD(map.get("avg_rating"), -1),
							-1,
							-1,
							tags);
					async_println("=> Parsed metadata.");
					async_println("Result: ");
					async_println(metadata.toString());
					async_println("=> Parsing videos...");
					final boolean signatureDecryptionRequired = result.contains("?s=") || result.contains("&s=");
					if (contains(options, YoutubeExtractionOptions.DISABLE_SEPERATE_STREAMS)) {
						async_println("! Will not be extracting seperate streams.");
					}
					if (!contains(options, YoutubeExtractionOptions.DISABLE_SEPERATE_STREAMS)) {
						final String comb = URIUtil.safeDecode(
								(map.get("url_encoded_fmt_stream_map") + "," + map.get("adaptive_fmts"))
								.replaceAll(Pattern.quote("&xtags=,"), ""));
						final String[] sources = comb.split("(,(?=\\S))");
						int fuckUps = 0;
						for (final String source : sources) {
							final Map<String, String> sourceList = URIUtil.queryStringToMap_NoDecode(source);
							String url0 = sourceList.get("url");
	            			if (url0 == null) {
	            				logger.error("[Fast Mode 2] Uhh, there is no reference to a base URL.");
	            				JVMUtil.async_println("String: " + source);
	            				continue;
	            			}
	            			String sig = null;
	            			if (!sourceList.containsKey("s") && !sourceList.containsKey("signature")
	            					&& !sourceList.containsKey("lsig")) {
	            				logger.error("[Fast Mode 2] Uhh, this URL has no signature information, skipped.");
	            				fuckUps += 1;
	            				continue;
	            			}else if (signatureDecryptionRequired || sourceList.containsKey("s")) {
	    						check_decipher(videoID, args);
	    						if (decipherer == null) {
	    							return null;
	    						}
	            				//sig = (JVMUtil.isArg("--Signatures.useLegacyJS") ? 
	            				//		sigDec.decrypt(sourceList.get("s")) : softSigDec.decrypt(sourceList.get("s")));
	            				//if (JVMUtil.isArg("--Signatures.useLegacyJS") && sigDec != null) {
	            				//	sig = sigDec.decrypt(sourceList.get("s"));
	            				//}else {
	            				//	sig = softSigDec.decrypt(sourceList.get("s"));
	            				//}
	            				sig = decipherer.decrypt(sourceList.get("s"));
	            			}else if (sourceList.containsKey("lsig")) {
	            				sig = sourceList.get("lsig");
	            			}else {
	            				sig = sourceList.get("signature");
	            			}
	            			if (sourceList.containsKey("lsig")) {
	            				url0 += "?lsig=" + sig;
	            			}else {
	            				url0 += "?signature=" + sig;
	            			}
	            			
	            			
	            			for (final Map.Entry<String, String> entry : sourceList.entrySet()) {
	            				if (entry.getKey().equals("s") || entry.getKey().equals("signature") || 
	            						entry.getKey().equals("lsig")
	            						|| entry.getKey().equals("type") || entry.getKey().equals("xtags")
	            						|| entry.getKey().equals("url") || entry.getKey().equals("quality")
	            						|| entry.getKey().equals("size") || entry.getKey().equals("index")
	            						|| entry.getKey().equals("quality_label")
	            						|| entry.getKey().equals("fps") || entry.getKey().equals("projection_type")
	            						|| entry.getKey().equals("primaries")
	            						|| entry.getKey().equals("init")
	            						|| entry.getKey().equals("eotf") || entry.getKey().equals("sp")) {
	            					continue;
	            				}
	            				url0 += ("&" + entry.getKey() + "=" + entry.getValue());
	            			}
	            			
	            			final int itag = MathUtil.safeParseI(sourceList.get("itag"), -1);
	            			results.add(new YoutubeVideo(url0, metadata, !(itag == 17 || itag == 36 ||
	            					itag == 5 || itag == 43 || itag == 18 || itag == 22), itag));
						}
					}else {
						final String[] sources = URIUtil.safeDecode(map.get("url_encoded_fmt_stream_map"))
								.split("(,(?=\\S))");
						int fuckUps = 0;
						for (final String source : sources) {
							final Map<String, String> sourceList = URIUtil.queryStringToMap_NoDecode(source);
							String url0 = sourceList.get("url");
	            			if (url0 == null) {
	            				continue;
	            			}
	            			String sig = null;
	            			if (!sourceList.containsKey("s") && !sourceList.containsKey("signature")
	            					&& !sourceList.containsKey("lsig")) {
	            				logger.error("[Fast Mode 2] Uhh, this URL has no signature information, skipped.");
	            				fuckUps += 1;
	            				continue;
	            			}else if (signatureDecryptionRequired || sourceList.containsKey("s")) {
	    						check_decipher(videoID, args);
	    						if (decipherer == null) {
	    							return null;
	    						}
	            				//sig = (JVMUtil.isArg("--Signatures.useLegacyJS") ? 
	            				//		sigDec.decrypt(sourceList.get("s")) : softSigDec.decrypt(sourceList.get("s")));
	            				//if (JVMUtil.isArg("--Signatures.useLegacyJS") && sigDec != null) {
	            				//	sig = sigDec.decrypt(sourceList.get("s"));
	            				//}else {
	            				//	sig = softSigDec.decrypt(sourceList.get("s"));
	            				//}
	            				sig = decipherer.decrypt(sourceList.get("s"));
	            			}else if (sourceList.containsKey("lsig")) {
	            				sig = sourceList.get("lsig");
	            			}else {
	            				sig = sourceList.get("signature");
	            			}
	            			if (sourceList.containsKey("lsig")) {
	            				url0 += "?lsig=" + sig;
	            			}else {
	            				url0 += "?signature=" + sig;
	            			}
	            			
	            			
	            			for (final Map.Entry<String, String> entry : sourceList.entrySet()) {
	            				if (entry.getKey().equals("s") || entry.getKey().equals("signature")
	            						|| entry.getKey().equals("lsig")
	            						|| entry.getKey().equals("type") || entry.getKey().equals("xtags")
	            						|| entry.getKey().equals("url") || entry.getKey().equals("quality")
	            						|| entry.getKey().equals("size") || entry.getKey().equals("index")
	            						|| entry.getKey().equals("quality_label")
	            						|| entry.getKey().equals("fps") || entry.getKey().equals("projection_type")
	            						|| entry.getKey().equals("primaries")
	            						|| entry.getKey().equals("init")
	            						|| entry.getKey().equals("eotf") || entry.getKey().equals("sp")
	            						|| entry.getKey().equals("bitrate") || entry.getKey().equals("stream_type")) {
	            					continue;
	            				}
	            				url0 += ("&" + entry.getKey() + "=" + entry.getValue());
	            			}
	            			
	            			final int itag = MathUtil.safeParseI(sourceList.get("itag"), -1);
	            			results.add(new YoutubeVideo(url0, metadata, !(itag == 17 || itag == 36 ||
	            					itag == 5 || itag == 43 || itag == 18 || itag == 22), itag));
						}
					}
					return results;
				}
			};
		}
		
		private static void check_decipher(final String videoID, final Entry[] args) {
			if (decipherer == null) {
				async_println("! Decipherer not found! Extracting data...");
				async_println("D => Connecting to YouTube...");
				try {
					String page = URIUtil2.PortedLegacyDownloader.downloadPage(
							URIUtil.safeCreate("https://www.youtube.com/watch?v=" + videoID), true, args).getB();
					String jsData = null;
					String ex = StringsUtil.substringBetweenO(page, "<script src=\"/yts/jsbin/player-", "\"");
					boolean usingFix = false;
					if (ex == null && page.contains("/yts/jsbin/player_ias")) {
						ex = StringsUtil.substringBetweenO(page,
								"<script src=\"/yts/jsbin/player_", "\"");
						usingFix = true;
					}
					if (ex == null) {
						final String newPageInfo = URIUtil.downloadPage(URIUtil.safeCreate(
		    					"https://www.youtube.com/watch?v=" + videoID + "&bpctr=" + System.currentTimeMillis()
		    					//+ (this.list != null ? "&list=" + this.list : "") +
		    					//(this.forceEnglish ? this.HL_EN : "")), //"https://www.youtube.com/verify_controversy?next_url=/" + (URIUtil.safeEncode("watch?v=" + videoID))), 
		        				), false, Proxy.NO_PROXY);
						page = newPageInfo;
		    			ex = StringsUtil.substringBetweenO(page, "<script src=\"/yts/jsbin/player-", "\"");
					}
//					if (ex == null) {
//						logger.error("Unable to get player JS!");
//						//if (this.logger.traceEnabled) {
//						//	this.logger.trace("Printing HTML...");
//						////	this.logger.debugC(pageMap);
//						//}
//						logger.info("*bzzzzzzzzzzzzzzzzzz* restart et");
//						return this.getStreamUrls();
//					}
					String url0 = "https://s.ytimg.com/yts/jsbin/player-" +
		    				ex;
					if (usingFix) {
						url0 = "https://s.ytimg.com/yts/jsbin/player_" +
			    				ex;
					}
					jsData = URIUtil.downloadPage(
		    				URIUtil.safeCreate(url0));
					decipherer = YTSig.accquire(jsData);
					async_println("D => Decipherer loaded.");
				}catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public static class YouTubePageExtractor {
		private static final Logger logger = LoggerFactory.getLogger(true, true);
		
		private static String generateURL(final String videoID, final YoutubeExtractionOptions... options) {
			 String ytInfoUrl = (contains(options, YoutubeExtractionOptions.ENFORCE_HTTP_FALLBACK))
					 ? "http://" : "https://";
		     ytInfoUrl += "www.youtube.com/watch?v=" + videoID;
		     if (contains(options, YoutubeExtractionOptions.ENFORCE_ENGLISH_YOUTUBE)) {
		    	 ytInfoUrl += "&hl=en";
		     }
		     return ytInfoUrl;
		}
		
		public static Task<LinkedList<YoutubeVideo>> generateTask(final String videoID, final YoutubeExtractionOptions... options) {
			return new Task<LinkedList<YoutubeVideo>>() {

				@Override
				public LinkedList<YoutubeVideo> run() {
					final LinkedList<YoutubeVideo> results = new LinkedList<YoutubeVideo>();
					async_println("=> Generating URL...");
					final URL url = URIUtil2.parseURL(generateURL(videoID, options));
					Entry[] args = new Entry[0];
					if (contains(options, YoutubeExtractionOptions.SEND_FAKE_COOKIE_DATA)
							|| contains(options, YoutubeExtractionOptions.ENFORCE_PRIVACY_MODE)) {
						args = ArrayUtils.add(args, new Entry("Cookie", StringsUtil.randomString(64)));
					}
					String result;
					try {
						async_println("=> Downloading metadata...");
						result = URIUtil2.PortedLegacyDownloader.downloadPage(url, true, args).getB()
								.replaceAll(Pattern.quote("\\u0026"), "&");
					}catch (Exception e) {
						e.printStackTrace();
						return null;
					}
					async_println("=> Parsing metadata...");
					return results;
				}
			};
		}
	}
	
	public static class YoutubeRedesignExtractor {
		//same but adds &pbj=1 to links to get JSON data.
		
		private static final Logger logger = LoggerFactory.getLogger(true, true);
		
		private static String generateURL(final String videoID, final YoutubeExtractionOptions... options) {
			 String ytInfoUrl = (contains(options, YoutubeExtractionOptions.ENFORCE_HTTP_FALLBACK))
					 ? "http://" : "https://";
		     ytInfoUrl += "www.youtube.com/watch?v=" + videoID;
		     if (contains(options, YoutubeExtractionOptions.ENFORCE_ENGLISH_YOUTUBE)) {
		    	 ytInfoUrl += "&hl=en";
		     }
		     ytInfoUrl += "&pbj=1";
		     return ytInfoUrl;
		}
		
	}
	
}
